/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component.coach;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;

import robocup.component.PConf;
import robocup.component.SConf;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.PConf.COMPARE_TYPE;
import robocup.component.actions.Action;
import robocup.component.actions.ChangePlayerTypeAction;
import robocup.component.infotypes.AuralInfo;
import robocup.component.infotypes.CoachPlayerSayInfo;
import robocup.component.infotypes.CoachVisualInfo;
import robocup.component.infotypes.PlayerTypesInfo;
import robocup.component.speechacts.MessageFactory;
import robocup.component.speechacts.MessageFactory.MESSAGES;
import robocup.component.tactics.FormationData;

/**
 * PlayerTypeAnalyzer analyzes the PConfs and ranks them by certain criteria.
 * Therefore it uses the <code>Comparable</code>-property of the <code>PConf
 * </code>, by which these can be sorted easily. The assignment of player-types
 * to agents is done here. This component subscribes to the <code>ptypeInfo
 * </code>-InfoReceiver-List of the OLCoach tactic, and unsubscribes, after all
 * changes are made.
 */
public class PlayerTypeAnalyzer implements InfoReceiver {

	
	/**
	 * Reference to the actual CoachWorld-Model
	 */
	private CoachWorld cwm;

	/**
	 * Reference to the actual coach-tactic.
	 */
	private OLCoachTactic olct;

	/**
	 * The number of available player-types
	 */
	private final int pcSize = 7;

	/**
	 * Number of players (teammates) on the field.
	 */
	private int plSize;

	/**
	 * The available player-configurations.
	 */
	private PConf[] allPConf;

	/**
	 * Counts the PConfs, which are actually playing
	 */
	private int ptypeIsAv[];

	/**
	 * Contains the assigned PConfs, refer to them by 
	 * playerNumber - 1
	 */
	private PConf[] playingTypes;

	
	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Kickable-Margin
	 */
	private int ranked_KM[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Dash-Power
	 */
	private int ranked_DP[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Inertia-Moment
	 */
	private int ranked_IM[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Stamina
	 */
	private int ranked_St[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by speed.
	 */
	private int ranked_Fast[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Kickable-Margin and Inertia-Moment (equally weighted)
	 */
	private int ranked_KM_IM[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Kiackable-Margin and Stamina (equally weighted)
	 */
	private int ranked_KM_St[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Dash-Power and Stamina (equally weighted)
	 */
	private int ranked_DP_St[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Dash-Power and Stamina (former weighted x 2)
	 */
	private int ranked_2DP_1St[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Dash-Power and Stamina (latter weighted x 2)
	 */
	private int ranked_1DP_2St[];

	/**
	 * An array containing the indizes of the player-types array, sorted
	 * by Kiackable-Margin, Dash-Power and Stamina (equally weighted)
	 */
	private int ranked_KM_DP_St[];

	/**
	 * Stores, which player has already a player-type assignment
	 */
	private boolean[] alreadyChanged = new boolean[11];

	/**
	 * Constant, which defines the amount of cycles before half-time, in which
	 * no player type should be changed. This is because after half-time,
	 * players have regenerated recovery.
	 */
	private double changeBeforeHalftime = 500;

	/**
	 * The number of performed change actions.
	 */
	private int changeAction = 0;

	/**
	 * De/Activates logging for player-type analyzation
	 */
	private boolean DEBUG_LEVEL_1 = false;

	/**
	 * De/Activates logging for player-type analyzation
	 */
	private boolean DEBUG_LEVEL_2 = false;

	/**
	 * Constructor - this class provides the means for setting the heterogene
	 * player-types of the own team.
	 * 
	 * @param OLCoachTactic -
	 *            the parent object
	 */
	public PlayerTypeAnalyzer(OLCoachTactic ol) {

		this.olct = ol;

		this.cwm = ol.getCoachWorld();

		this.plSize = cwm.getTeammatesCount();

		this.allPConf = new PConf[this.pcSize];

		this.ptypeIsAv = new int[this.pcSize];
		this.playingTypes = new PConf[this.plSize];

		this.ranked_KM = new int[this.pcSize];
		this.ranked_DP = new int[this.pcSize];
		this.ranked_IM = new int[this.pcSize];
		this.ranked_St = new int[this.pcSize];
		this.ranked_Fast = new int[this.pcSize];
		this.ranked_KM_IM = new int[this.pcSize];
		this.ranked_KM_St = new int[this.pcSize];
		this.ranked_DP_St = new int[this.pcSize];
		this.ranked_2DP_1St = new int[this.pcSize];
		this.ranked_1DP_2St = new int[this.pcSize];
		this.ranked_KM_DP_St = new int[this.pcSize];
		Arrays.fill(alreadyChanged, false);
	}

	/**
	 * Called at the arrival of an AuralInfo.
	 * 
	 * @param Info - the latest aural-info
	 */
	public void receivedAuralInfo(AuralInfo info) {
		if (info.toString().startsWith("PlayerMessage:")) {
			CoachPlayerSayInfo cpsiInfo = ((CoachPlayerSayInfo) info);
			update(cpsiInfo);
		}
	}

	/**
	 * Called at the arrival of a player-type-info. Since this component
	 * is subscribed for player-type-info, they will be received here. If
	 * all types are available (7), they will be analyzed and assigned.
	 * 
	 * @param Info - the latest player-type-info
	 */
	public void receivedPlayerTypeInf(PlayerTypesInfo info) {
		// all playertypes available
		if (info.playerTypesFull()) {
			allPConf = info.getPlayerTypes();
			playingTypes = this.analyze(allPConf);

			// map Players with playingTypes
			for (int i = 0; i < playingTypes.length; i++) {
				int id = playingTypes[i].getId();
				Action a = new ChangePlayerTypeAction(i + 1, id);
				olct.sendDirect(a.toString());
			}
		}
	}

	/**
	 * Called at the arrival of a visual-info. Is not used.
	 * 
	 * @param info - the latest visual-info.
	 */
	public void receivedVisualInfo(CoachVisualInfo info) {
	}

	/**
	 * Is used, if a message from the players is heared. If they communicate
	 * their recovery, this will be noticed for changes during the game.
	 * 
	 * @param cpsi - the message-object of the player
	 */
	public void update(CoachPlayerSayInfo cpsi) {
		MessageFactory mf = cwm.getMessageFactory();
		mf.generateMessages(cpsi.getMessage(), cwm.getServerCycle());
		LinkedList<MESSAGES> types = mf.getMessageTypes();

		for (int i = 0; i < types.size(); i++) {
			MESSAGES m = types.get(i);

			switch (m) {

			case RECOVERY:
				mf.decodeMessageNumber(i);
				if (mf.getRecovery() < 0.7
						&& !alreadyChanged[mf.getNumber() - 1])
					changePlayerType(mf.getNumber(), playingTypes[mf
							.getNumber() - 1].getId());

				break;
			}
		}
	}

	/**
	 * Changes Playertype<br><br>
	 * 
	 * Checks if it is possible to execute a change-player action by certain 
	 * criteria. Changes only if Play Mode is not Play_on, changeaction is 
	 * below 3 times, change-action did not happen this play mode and 
	 * change-action is performed only in the second halftime
	 * 
	 * @param int
	 *            player-number for changing action
	 * @param int
	 *            The new Player-type index given to player
	 * 
	 */
	public void changePlayerType(int number, int type) {
		if ((this.cwm.getPlayMode() != PLAY_MODE.PLAY_ON)
				&& (changeAction < 3)
				&& ((cwm.getVisualCycle() > (SConf.getInstance().HALF_TIME * 10)))
				|| (cwm.getVisualCycle() + changeBeforeHalftime < (SConf
						.getInstance().HALF_TIME * 10))) {
			Action a = new ChangePlayerTypeAction(number, type);
			olct.sendDirect(a.toString());
			changeAction++;
			alreadyChanged[number - 1] = true;
			System.out.println(cwm.getVisualCycle() + " Changed Player "
					+ number + " to type: " + type);
		}

	}

	/**
	 * The PConf analyzer - this method analyzes all player-types
	 * by sorting them using different sort-criteria. After sorting
	 * the order is remembered in the corresponding arrays (e.g.
	 * <code>ranked_XY</code>).<br><br>
	 * 
	 * Note that each type can be assigned only once.
	 * 
	 * @param PConfs
	 *            given by server (7 by default)
	 * @return PConf after analyzing it (11, each Player gets one)
	 */
	public PConf[] analyze(PConf[] all) {

		// tell my world model about the available player types
		this.cwm.setPlayerTypes(all);

		LinkedList<PConf> pConfs = new LinkedList<PConf>();
		for (PConf pC : all)
			pConfs.add(pC);

		// sorting PConf-Array by Kickable Margin
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.KICKABLE_DISTANCE);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_KM[i] = pConfs.get(i).getId();

		// sorting PConf-Array by Dash Power Rate
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.DASH_POWER);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_DP[i] = pConfs.get(i).getId();

		// sorting PConf-Array by Max Speed (decay and accel)
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.SPEED);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_Fast[i] = pConfs.get(i).getId();

		// sorting PConf-Array by INERTIA_MOMENT
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.INERTIA);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_IM[i] = pConfs.get(i).getId();

		// sorting PConf-Array by Extra Stamina
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.STAMINA);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_St[i] = pConfs.get(i).getId();

		// value weighting Kickable Margin - Stamina
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.STAMINA_AND_KICKABLE);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_KM_St[i] = pConfs.get(i).getId();

		// value weighting one-to-one Kickable Margin - Dash Power Rate -
		// Stamina
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.KICKABLE_AND_DASH_POWER_AND_STAMINA);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_KM_DP_St[i] = pConfs.get(i).getId();
		//
		// // value weighting Dash Power - Stamina
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.DASH_POWER_AND_STAMINA);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_DP_St[i] = pConfs.get(i).getId();
		//
		// // value weighting Kickable Margin - Stamina
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.KICKABLE_AND_INERTIA);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_KM_IM[i] = pConfs.get(i).getId();
		//		
		// // value weighting 1:2 Dash Power - Stamina
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.DASH_POWER_AND_DOUBLE_STAMINA);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_1DP_2St[i] = pConfs.get(i).getId();
		//		
		// // value weighting 2:1 Dash Power - Stamina
		for (PConf pC : pConfs)
			pC.setCompareType(COMPARE_TYPE.DOUBLE_DASH_POWER_AND_STAMINA);
		Collections.sort(pConfs);
		for (int i = 0; i < pConfs.size(); i++)
			ranked_2DP_1St[i] = pConfs.get(i).getId();

		this.playingTypes = FormationData.getPConfForPlayers(this);
		this.print();
		return this.playingTypes;
	}

	/**
	 * Prints some debug values for player-type assignment to the screen.
	 * Set the debug-level to activate this (DEBUG_LEVEL_1, DEBUG_LEVEL_2)
	 */
	public void print() {
		if (!DEBUG_LEVEL_1)
			return;
		// Print Values of different Heterogenous Types
		StringBuffer buf = new StringBuffer();
		buf
				.append("\n I   Decay\tKickM\tIner\tExStam\tStamInc\tKRand\tAcMax\tSpdMx\tDashPower\tAssigned\n");
		for (int i = 0; i < allPConf.length; i++) {
			buf.append(" " + allPConf[i].getId() + "   ");
			buf.append(allPConf[i].PLAYER_DECAY);
			buf.append("\t");
			buf.append(allPConf[i].KICKABLE_MARGIN);
			buf.append("\t");
			buf.append(allPConf[i].INERTIA_MOMENT);
			buf.append("\t");
			buf.append(allPConf[i].EXTRA_STAMINA);
			buf.append("\t");
			buf.append(allPConf[i].STAMINA_INC_MAX);
			buf.append("\t");
			buf.append(allPConf[i].KICK_RAND);
			buf.append("\t");
			buf.append(allPConf[i].PLAYER_ACCEL_MAX);
			buf.append("\t");
			buf
					.append(Math
							.floor(allPConf[i].getEffectiveMaxSpeed() * 1000) / 1000.0);
			buf.append("\t");
			buf.append(allPConf[i].DASH_POWER_RATE + "    ");
			buf.append("\t");

			for (int j = 0; j < playingTypes.length; j++)
				if (playingTypes[j].getId() == allPConf[i].getId())
					buf.append((j + 1) + " ");
			buf.append('\n');

		}
		System.out.println(buf.toString());

		if (!DEBUG_LEVEL_2)
			return;
		// Print Values for research
		buf = new StringBuffer();
		buf.append("\n Ranking Type           PConf Position\n");
		buf.append(" ______________________  0 1 2 3 4 5 6\n\n");
		buf.append(" Ranked_KM: \t\t");
		for (int i : this.ranked_KM)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_DP: \t\t");
		for (int i : this.ranked_DP)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_IM: \t\t");
		for (int i : this.ranked_IM)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_St: \t\t");
		for (int i : this.ranked_St)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_KM_IM: \t\t");
		for (int i : this.ranked_KM_IM)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_KM_St: \t\t");
		for (int i : this.ranked_KM_St)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_DP_St: \t\t");
		for (int i : this.ranked_DP_St)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_2DP_1St: \t");
		for (int i : this.ranked_2DP_1St)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_1DP_2St: \t");
		for (int i : this.ranked_1DP_2St)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_KM_DP_St: \t");
		for (int i : this.ranked_KM_DP_St)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Ranked_FAST \t\t");
		for (int i : this.ranked_Fast)
			buf.append(" " + i);
		buf.append(" \n");

		buf.append(" Players in Action: \t");
		for (int i : this.ptypeIsAv)
			buf.append(" " + i);
		buf.append(" \n _________________powered by DAINAMITE\n\n");
		System.out.println(buf.toString());

	}

	/**
	 * Getter for the array containing the available player types. Usually,
	 * each type is available three times.
	 * 
	 * @return int[] available types.
	 */
	public int[] getPtypeIsAv() {
		return this.ptypeIsAv;
	}

	/**
	 * Sets teh available player-types.
	 * 
	 * @param pTypeAv - an array containing the available player-types.
	 */
	public void setPtypeIsAv(int[] pTypeAv) {
		this.ptypeIsAv = pTypeAv;
	}
	
	/**
	 * Retrieves the order of the player-types, sorted by
	 * Kickable-Margin.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_KM() {
		return this.ranked_KM;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Dash-Power.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_DP() {
		return this.ranked_DP;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Dash-Power and Stamina (dash-power weighted higher)
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_2DP_1St() {
		return this.ranked_2DP_1St;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Dash-Power and Stamina (stamina weighted higher)
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_1DP_2St() {
		return this.ranked_1DP_2St;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Inertia Moment.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_IM() {
		return this.ranked_IM;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Dash-Power and Stamina.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_DP_St() {
		return this.ranked_DP_St;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Kickable-Margin, Dash-Power and Stamina.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_KM_DP_St() {
		return this.ranked_KM_DP_St;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Kickable-Margin and Stamina.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_KM_St() {
		return this.ranked_KM_St;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Kickable-Margin and Inertia-Moment.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_KM_IM() {
		return this.ranked_KM_IM;
	}

	/**
	 * Retrieves the order of the player-types, sorted by
	 * Stamina.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_St() {
		return this.ranked_St;
	}
	
	
	/**
	 * Retrieves the order of the player-types, sorted by
	 * speed.
	 * 
	 * @return int[] - the indizes of the player-types
	 */
	public int[] getRanked_Fast() {
		return this.ranked_Fast;
	}


	/**
	 * Getter
	 * 
	 * @return int - the number of available player-types.
	 */
	public int getPcSize() {
		return this.pcSize;
	}

	/**
	 * Returns all received player-types.
	 * 
	 * @return the array containing all player-types.
	 */
	public PConf[] getAllPConf() {
		return this.allPConf;
	}

}
